package com.example.demo;

import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.List;

public class DynamicFeatureClassGeneratorV1 {

    public static final String CLASS_PATH;

    static {
        CLASS_PATH = "/data/com/credit_rule_service";
//        CLASS_PATH = DynamicFeatureClassGenerator.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        // String,Integer,Char,Double,Long,Float,BigDecimal,Boolean,Date,List,Set,Map,Enum,Object;
    }

    /**
     * 解析特定的json @see bytecode-template.json
     **/
    public static void generateFeatureClassFromJson(FeatureTemplateClassGenerateReq featureTemplate) throws Exception {

        String className = featureTemplate.getClazz();
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass;
        try {
            ctClass = pool.get(className);
            // 如果已经加载了，jvm不允许在修改，会抛出frozen错误，这里先解冻
            ctClass.defrost();
            ctClass.detach();
        } catch (Exception e) {
            // pool.get如果找不到会抛错，这里直接忽略即可
        }
        ctClass = pool.makeClass(className);
        if (StringUtils.isNotBlank(featureTemplate.getParentClazz())) {
            ctClass.setSuperclass(pool.get(featureTemplate.getParentClazz()));
        }
        // 添加默认构造函数
        CtConstructor constructor = CtNewConstructor.defaultConstructor(ctClass);
        ctClass.addConstructor(constructor);
        // 构建类注解
        parseClassAnnotationMeta(featureTemplate, ctClass);

        // 构造属性参数
        parseFieldsMeta(featureTemplate, pool, ctClass);
        ctClass.writeFile(CLASS_PATH);
        System.out.println("生成类成功-------" + CLASS_PATH);

    }

    /**
     * 构造熟悉参数
     *
     * @param traitsTemplate
     * @param pool
     * @param ctClass
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private static void parseFieldsMeta(FeatureTemplateClassGenerateReq traitsTemplate, ClassPool pool, CtClass ctClass) throws CannotCompileException, NotFoundException {
        // 解析属性参数
        List<FeatureTemplateClassGenerateReq.BeanFieldTemplate> fields = traitsTemplate.getFields();
        if (!CollectionUtils.isEmpty(fields)) {
            for (FeatureTemplateClassGenerateReq.BeanFieldTemplate field : fields) {
                String fieldName = field.getFieldName();
                String defaultValue = field.getDefaultValue();
                TemplateDataType datatype = TemplateDataType.getFieldTypeByCode(field.getType());
                CtField ctField = new CtField(pool.get(datatype.getClazz().getName()), fieldName, ctClass);

                ctField.setModifiers(Modifier.PRIVATE);
                ctClass.addField(ctField);
                String methodName = "set" + capitalize(fieldName);
                // 添加对应的 setterMethod 方法，并设置默认值
                CtMethod setterMethod = CtNewMethod.setter(methodName, ctField);
                // 设置body public void setName(String name) this.name=name  $1;等价于去第一个值
                setterMethod.setBody("{ this." + fieldName + " = $1; }");
                ctClass.addMethod(setterMethod);

                // 添加对应的 getter 方法
                CtMethod getter = CtNewMethod.getter("get" + capitalize(fieldName), ctField);
                ctClass.addMethod(getter);
                // 添加参数上的注解信息
                parseFieldAnnotationMeta(field, ctField);
            }
        }
    }

    // 解析类注解
    private static void parseClassAnnotationMeta(FeatureTemplateClassGenerateReq template, CtClass ctClass) {
        // 添加 @NotBlank 注解到 name 字段
        if (CollectionUtils.isEmpty(template.getAnnotations())) {
            return;
        }
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        for (FeatureTemplateClassGenerateReq.AnnotationTemplate annotationTemplate : template.getAnnotations()) {
            AnnotationsAttribute classAttr = buildAnnotationsAttribute(annotationTemplate, constPool);
            if (null != classAttr) {
                ctClass.getClassFile().addAttribute(classAttr);
            }
        }
    }

    // 解析参数注解
    private static void parseFieldAnnotationMeta(FeatureTemplateClassGenerateReq.BeanFieldTemplate field, CtField ctField) {
        if (CollectionUtils.isEmpty(field.getAnnotations())) {
            return;
        }
        ConstPool constPool = ctField.getFieldInfo().getConstPool();
        for (FeatureTemplateClassGenerateReq.AnnotationTemplate annotationTemplate : field.getAnnotations()) {
            AnnotationsAttribute annotationsAttribute = buildAnnotationsAttribute(annotationTemplate, constPool);
            if (null != annotationsAttribute) {
                ctField.getFieldInfo().addAttribute(annotationsAttribute);
            }
        }
    }

    /**
     * 构建注解参数
     *
     * @param annotationTemplate
     * @param constPool
     * @return
     */
    private static AnnotationsAttribute buildAnnotationsAttribute(FeatureTemplateClassGenerateReq.AnnotationTemplate annotationTemplate, ConstPool constPool) {
        // lombok的先不处理 里面需要根据每个注解生产不同的逻辑
        if (annotationTemplate.getAnnotationType().startsWith("lombok.")) {
            return null;
        }
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(annotationTemplate.getAnnotationType(), constPool);
        // 填充注解属性值，例如 value等参数
        if (!CollectionUtils.isEmpty(annotationTemplate.getParamArray())) {
            for (FeatureTemplateClassGenerateReq.AnnotationTemplate.AnnotationParam annotationParam : annotationTemplate.getParamArray()) {

                // 注解设置时， 类型名字值三要素缺一不可
                if (StringUtils.isBlank(annotationParam.getValueType()) ||
                        StringUtils.isBlank(annotationParam.getValueName()) ||
                        StringUtils.isBlank(annotationParam.getValue())) {
                    continue;
                }
                Datatype datatype = Datatype.valueOf(annotationParam.getValueType());
                switch (datatype) {
                    case String:
                        annotation.addMemberValue(annotationParam.getValueName(), new StringMemberValue(annotationParam.getValue(), constPool));
                        break;
                    case Integer:
                        annotation.addMemberValue(annotationParam.getValueName(), new IntegerMemberValue(constPool, Integer.parseInt(annotationParam.getValue())));
                        break;
                    case Char:
                        annotation.addMemberValue(annotationParam.getValueName(), new CharMemberValue(annotationParam.getValue().charAt(0), constPool));
                        break;
                    case Double:
                        annotation.addMemberValue(annotationParam.getValueName(), new DoubleMemberValue(Double.parseDouble(annotationParam.getValue()), constPool));
                        break;
                    case Long:
                        annotation.addMemberValue(annotationParam.getValueName(), new LongMemberValue(Long.parseLong(annotationParam.getValue()), constPool));
                        break;
                    case Float:
                        annotation.addMemberValue(annotationParam.getValueName(), new FloatMemberValue(Float.parseFloat(annotationParam.getValue()), constPool));
                        break;
                    case Boolean:
                        annotation.addMemberValue(annotationParam.getValueName(), new BooleanMemberValue(Boolean.parseBoolean(annotationParam.getValue()), constPool));
                        break;
                    default:
                        throw new IllegalStateException("Unexpected value: " + datatype);
                }
            }
        }
        annotationsAttribute.addAnnotation(annotation);
        return annotationsAttribute;
    }

    private static String capitalize(String str) {
        if (str == null || str.isEmpty()) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }


}
